/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "LivemountServiceJob.h"
#include <chrono>
#include "ApplicationProtectPlugin_types.h"
#include "ApplicationProtectBaseDataType_types.h"
#include "ApplicationProtectFramework_types.h"
#include "ClientInvoke.h"
#include "common/CleanMemPwd.h"
#include "LivemountJobManager.h"
#include "PluginConstants.h"
#include "PluginUtilities.h"
#include "log/Log.h"

using namespace std;
using namespace AppProtect;
using namespace Module;

namespace {
    constexpr auto MODULE = "NASLivemount";
    constexpr auto NOINVALIDIPERRMSG = "No invalid service ip!";
    constexpr auto CYBER_ENGINE_POOL_INTERVAL = 10s;
    constexpr auto CYBER_ENGINE_RETRY_COUNT = 6;
    const int PROGRESS90 = 90;
    const int NUM2 = 2;
}

bool LivemountServiceJob::GetJobInfoBody()
{
    if (GetJobInfo() != nullptr) {
        m_livemountPara = dynamic_pointer_cast<AppProtect::LivemountJob>(GetJobInfo()->GetJobInfo());
    }
    if (m_livemountPara == nullptr) {
        ERRLOG("GetJobInfoBody failed, m_livemountPara is nullptr");
        return false;
    }
    return true;
}

LivemountServiceJob::~LivemountServiceJob()
{
    if (m_dataRepos.size() > 0) {
        for (auto repo : m_dataRepos) {
            if (repo) {
                CleanMemoryPwd(repo->auth.authPwd);
                CleanMemoryPwd(repo->auth.extendInfo);
            }
        }
    }
}

EXTER_ATTACK int LivemountServiceJob::CheckBackupJobType()
{
    SetJobToFinish();
    return SUCCESS;
}

EXTER_ATTACK int LivemountServiceJob::PrerequisiteJob()
{
    int ret = PrerequisiteJobInner();
    SetJobToFinish();
    return ret;
}

EXTER_ATTACK int LivemountServiceJob::GenerateSubJob()
{
    int ret = GenerateSubJobInner();
    SetJobToFinish();
    return ret;
}

EXTER_ATTACK int LivemountServiceJob::ExecuteSubJob()
{
    int ret = ExecuteSubJobInner();
    SetJobToFinish();
    return ret;
}

EXTER_ATTACK int LivemountServiceJob::PostJob()
{
    int ret = PostJobInner();
    SetJobToFinish();
    return ret;
}

int LivemountServiceJob::PrerequisiteJobInner() const
{
    return SUCCESS;
}

int LivemountServiceJob::GenerateSubJobInner()
{
    if (!GetJobInfoBody()) {
        return FAILED;
    }
    HCP_Log(INFO, MODULE) << "Generate sub task for live mount task, task id is " <<
        m_livemountPara->jobId << HCPENDLOG;
    SubJob subJob {};
    subJob.__set_jobId(m_livemountPara->jobId);
    subJob.__set_jobType(SubJobType::BUSINESS_SUB_JOB);
    subJob.__set_jobName("livemount");
    subJob.__set_policy(ExecutePolicy::ANY_NODE);
    vector<SubJob> vec {};
    vec.push_back(subJob);

    ActionResult result {};
    JobService::AddNewJob(result, vec);
    SubJobDetails subJobDetails {};
    subJobDetails.__set_jobId(m_livemountPara->jobId);
    subJobDetails.__set_jobStatus(SubJobStatus::COMPLETED);
    string description = "Generate sub task for live mount task successfully";
    LogDetail logDetail {};
    vector<LogDetail> logDetails {};
    logDetail.__set_description(description);
    logDetails.push_back(logDetail);
    subJobDetails.__set_logDetail(logDetails);
    JobService::ReportJobDetails(result, subJobDetails);
    SetJobToFinish();
    HCP_Log(INFO, MODULE) << "Finish to generate sub job , main task id is " << m_livemountPara->jobId << HCPENDLOG;
    return SUCCESS;
}

int LivemountServiceJob::ExecuteSubJobInner()
{
    HCP_Log(INFO, MODULE) << "ExecuteSubJob: main job id is " << m_parentJobId << ", subJob id is "
        << m_subJobInfo->subJobId << HCPENDLOG;
    int ret = IdentifyRepos();
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    ActionResult result;
    std::vector<LogDetail> logDetailList;
    if (ret != SUCCESS) {
        HCP_Log(ERR, MODULE) << "Bad Identify Repos" << HCPENDLOG;
        AddLogDetail(logDetail, "", JobLogLevel::TASK_LOG_ERROR);
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::FAILED);
        return FAILED;
    }
    ret = ReportMountLabel();
    if (ret != SUCCESS) {
        ERRLOG("Report Mount label failed!");
        return ret;
    }
    // 安全一体机需求， 这里不上报完成， 任务一直挂起， 进度上报成90， 等接受到PM的指令再上报完成.
    if (PluginUtils::GetDeployType() == SECURITY_MACHINE_DEPLOY_TYPE) {
        ret = HandleProcessForCyberEngine(CYBER_ENGINE_RETRY_COUNT);
        if (ret != SUCCESS) {
            ERRLOG("timeout for job: %s", m_parentJobId.c_str());
            AddLogDetail(logDetail, "", JobLogLevel::TASK_LOG_ERROR);
            REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::FAILED);
            return FAILED;
        }
    } else {
        // 安全一体机不调用这个接口
        // DTS2023120521769 调用上报副本信息接口不能有cache仓， ubc校验不过， 会导致接口调用卡住
        for (int i = 0; i < m_livemountPara->copy.repositories.size(); i++) {
            if (m_livemountPara->copy.repositories[i].repositoryType == RepositoryDataType::type::CACHE_REPOSITORY) {
                auto it = m_livemountPara->copy.repositories.begin();
                m_livemountPara->copy.repositories.erase(it + i);
                break;
            }
        }
        INFOLOG("after erase : %d", m_livemountPara->copy.repositories.size());
        JobService::ReportCopyAdditionalInfo(result, m_parentJobId, m_livemountPara->copy);
    }
    AddLogDetail(logDetail, "", JobLogLevel::TASK_LOG_INFO);
    REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::COMPLETED);
    HCP_Log(INFO, MODULE) << "Finish to execute sub job, main job id is " << m_parentJobId
        << ", sub task id is " << m_subJobInfo->subJobId << HCPENDLOG;
    return SUCCESS;
}

int LivemountServiceJob::HandleProcessForCyberEngine(int maxRetryCnt)
{
    INFOLOG("Enter HandleProcessForCyberEngine! %s", m_parentJobId.c_str());
    LivemountJobExtendInfo extendInfo;
    if (!JsonHelper::JsonStringToStruct(m_livemountPara->extendInfo, extendInfo)) {
        HCP_Log(ERR, MODULE) << "job extend info is invaild." << HCPENDLOG;
    }
    INFOLOG("parse extend info : %d", extendInfo.performance.fileSystemKeepTime);
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    ActionResult result;
    std::vector<LogDetail> logDetailList;
    AddLogDetail(logDetail, "nas_plugin_livemount_cyberengine_label", JobLogLevel::TASK_LOG_WARNING,
        to_string(extendInfo.performance.fileSystemKeepTime));
    REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, PROGRESS90, 0, SubJobStatus::RUNNING);
    LivemountJobManager::GetInstance().RegisterJob(m_parentJobId, LivemountJobStatus::PENDING);
    while (true) {
        INFOLOG("Waiting for complete signal!, %s", m_parentJobId.c_str());
        AddLogDetail(logDetail, "", JobLogLevel::TASK_LOG_INFO);
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, PROGRESS90, 0, SubJobStatus::RUNNING);
        std::this_thread::sleep_for(CYBER_ENGINE_POOL_INTERVAL);
        if (LivemountJobManager::GetInstance().GetJobStatus(m_parentJobId) == LivemountJobStatus::SUCCESS) {
            INFOLOG("Job success , %s", m_parentJobId.c_str());
            break;
        }
        if (LivemountJobManager::GetInstance().GetJobStatus(m_parentJobId) == LivemountJobStatus::FAILED) {
            ERRLOG("Job failed , %s", m_parentJobId.c_str());
            return FAILED;
        }
    }
    return SUCCESS;
}

int LivemountServiceJob::ReportMountLabel()
{
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    ActionResult result;
    std::vector<LogDetail> logDetailList;
    string sharePath;
    for (auto repo : m_dataRepos) {
        string shareIp;
        for (auto hostAddress : repo->remoteHost) {
            shareIp += hostAddress.ip + ", ";
        }
        shareIp = shareIp.substr(0, shareIp.length() - NUM2);
        INFOLOG("shareIp : %s", shareIp.c_str());
        if (repo->protocol == RepositoryProtocolType::type::NFS) {
            HCP_Log(INFO, MODULE) << "report nfs mount path " << HCPENDLOG;
            sharePath = repo->remotePath;
            AddLogDetail(logDetail, "nas_plugin_livemount_nfs_mountinfo_label", JobLogLevel::TASK_LOG_INFO,
                sharePath, shareIp);
            REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);
        } else if (repo->protocol == RepositoryProtocolType::type::CIFS) {
            HCP_Log(INFO, MODULE) << "report cifs mount path " << HCPENDLOG;
            LiveMountStorageRepositoryExtendInfo extendInfo {};
            if (!JsonHelper::JsonStringToStruct(repo->extendInfo, extendInfo)) {
                HCP_Log(ERR, MODULE) << "Data storage repository extend info is invaild." << HCPENDLOG;
                return FAILED;
            }
            sharePath = extendInfo.fileSystemShareInfo.advanceParams.shareName;
            AddLogDetail(logDetail, "nas_plugin_livemount_cifs_mountinfo_label", JobLogLevel::TASK_LOG_INFO,
                sharePath, shareIp);
            REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);
        }
    }
    return SUCCESS;
}

// 识别cache repo , pre data repo , cur data repo
int LivemountServiceJob::IdentifyRepos()
{
    if (!GetJobInfoBody()) {
        return FAILED;
    }
    for (auto repo : m_livemountPara->copy.repositories) {
        if (repo.repositoryType == RepositoryDataType::type::DATA_REPOSITORY) {
            auto storageRepo = std::make_shared<StorageRepository>(repo);
            HCP_Log(INFO, MODULE) << "recognize data repo : " << WIPE_SENSITIVE(storageRepo->remotePath);
            m_dataRepos.push_back(storageRepo);
        }
    }
    if (m_dataRepos.size() > 0) {
        return SUCCESS;
    }
    HCP_Log(ERR, MODULE) << "repo init not complete" << HCPENDLOG;
    return FAILED;
}

int LivemountServiceJob::PostJobInner() const
{
    return SUCCESS;
}